# include "dfxProgram_OGL.h"
# include "dfxTexture_OGL.h"
# include "dfxLog.h"
# include "dfxGraphicsDevice_OGL.h"

namespace DFX
{
    Program_OGL::Program_OGL(GraphicsDevice *gd, const String &name)
        : Program(gd, name), mHandle(0)
    {
        for (UInt32 i = 0; i < SHADER_TYPE_MAX; i++)
            mShaders[i] = 0;

        if (mHandle)
            glDeleteProgram(mHandle);

        mHandle = glCreateProgram();
    }

    Program_OGL::~Program_OGL()
    {
        Destroy();
    }

    DFXRESULT Program_OGL::LoadShader(SHADER_TYPE shaderType, InputStream *stream)
    {
        if (mShaders[shaderType])
            glDeleteShader(mShaders[shaderType]);

        GLuint handle = glCreateShader(GraphicsDevice_OGL::ShaderType[shaderType]);

        if (!handle)
        {
            DFX_ERROR( "Unable to create shader");
            return DFX_LOADFAILED;
        }

        const GLchar *src = (const GLchar*)stream->MapStream();
        const GLint size = (const GLint)stream->GetSize();

        glShaderSource(handle, 1, &src, &size);
        glCompileShader(handle);

        GLint status = GL_FALSE;
        glGetShaderiv(handle, GL_COMPILE_STATUS, &status);

        if (status == GL_FALSE)
        {
            DFX_ERROR("Unable to compile vertex shader for program %s", mName.c_str());

            GLint logLen;
            glGetShaderiv(handle, GL_INFO_LOG_LENGTH, &logLen);

            if (logLen > 0)
            {
                GLchar *log = (GLchar*)malloc(logLen);
                glGetShaderInfoLog(handle, logLen, &logLen, log);

                DFX_ERROR("Compilation error: %s", 
                    mName.c_str(), log);
            }

            return DFX_LOADFAILED;
        }

        mShaders[shaderType] = handle;

        return DFX_SUCCESS;    
    }

    DFXRESULT Program_OGL::Link()
    {
        for (UInt32 i = 0; i < SHADER_TYPE_MAX; i++)
        {
            if (mShaders[i])
                glAttachShader(mHandle, mShaders[i]);
        }

        glLinkProgram(mHandle);

        GLint status = GL_FALSE;
        glGetProgramiv(mHandle, GL_LINK_STATUS, &status);

        if (status == GL_FALSE)
        {
            DFX_ERROR("Unable to link program %s", mName.c_str());

            GLint logLen;
            glGetProgramiv(mHandle, GL_INFO_LOG_LENGTH, &logLen);

            if (logLen > 0)
            {
                GLchar *log = (GLchar*)malloc(logLen);
                glGetProgramInfoLog(mHandle, logLen, &logLen, log);

                DFX_ERROR("Linking error: %s", log);
            }

            return DFX_LOADFAILED;
        }

        status = GL_FALSE;
        glValidateProgram(mHandle);

        glGetProgramiv(mHandle, GL_VALIDATE_STATUS, &status);

        if (status == GL_FALSE)
        {
            DFX_ERROR("Unable to validate program %s", mName.c_str());

            GLint logLen;
            glGetProgramiv(mHandle, GL_INFO_LOG_LENGTH, &logLen);

            if (logLen > 0)
            {
                GLchar *log = (GLchar*)malloc(logLen);
                glGetProgramInfoLog(mHandle, logLen, &logLen, log);

                DFX_ERROR( "Validating error: %s", log);
            }

            return DFX_LOADFAILED;
        }

        GLint   maxUniformLen;
        GLint   numUniforms;
        GLchar  *uniformName;

        glGetProgramiv(mHandle, GL_ACTIVE_UNIFORMS, &numUniforms);
        glGetProgramiv(mHandle, GL_ACTIVE_UNIFORM_MAX_LENGTH,
            &maxUniformLen);

        uniformName = (GLchar*)malloc(sizeof(GLchar)* maxUniformLen);

        for (GLint i = 0; i < numUniforms; i++)
        {
            GLint size;
            GLenum type;

            glGetActiveUniform(mHandle, i, maxUniformLen, NULL,
                &size, &type, uniformName);

            mShaderMap[uniformName] = glGetUniformLocation(mHandle, uniformName);
        }

        GLint maxAttribLen;
        GLint numAttribs;
        GLchar *attribName;

        glGetProgramiv(mHandle, GL_ACTIVE_ATTRIBUTES, &numAttribs);
        glGetProgramiv(mHandle, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH,
            &maxAttribLen);

        attribName = (GLchar*)malloc(sizeof(GLchar)* maxAttribLen);

        for (GLint i = 0; i < numAttribs; i++)
        {
            GLint size;
            GLenum type;

            glGetActiveAttrib(mHandle, i, maxAttribLen, NULL,
                &size, &type, attribName);

            mShaderMap[attribName] = glGetAttribLocation(mHandle, attribName);
        }

        return DFX_SUCCESS;
    }

    DFXRESULT Program_OGL::Destroy()
    {
        if (!mHandle)
            return DFX_ERROR0;

        for (UInt32 i = 0; i < SHADER_TYPE_MAX; i++)
        {
            if (mShaders[i])
                glDeleteShader(mShaders[i]);
        }

        glDeleteProgram(mHandle);

        return DFX_SUCCESS;
    }

    void Program_OGL::Bind()
    {
        if (mHandle)
            glUseProgram(mHandle);
    }

    void Program_OGL::BindVertexFormat(VertexFormat *format)
    {
        UInt32 numElements = format->GetNumElements();

        for (UInt32 i = 0; i < numElements; i++)
        {
            VertexElement *e = format->GetElement(i);

            glBindAttribLocation(mHandle, e->GetIndex(),
                e->GetName().c_str());
        }
    }

    UInt32 Program_OGL::GetConstantLocation(const String &name)
    {
        return mShaderMap[name];
    }

    void Program_OGL::SetVector2(UInt32 loc, const Vector2 &v)
    {
        glUniform2f(loc, v.X, v.Y);
    }

    void Program_OGL::SetVector3(UInt32 loc, const Vector3 &v)
    {
        glUniform3f(loc, v.X, v.Y, v.Z);
    }

    void Program_OGL::SetVector4(UInt32 loc, const Vector4 &v)
    {
        glUniform4f(loc, v.X, v.Y, v.Z, v.W);
    }

    void Program_OGL::SetVector2(UInt32 loc, Float32 x, Float32 y)
    {
        glUniform2f(loc, x, y);
    }

    void Program_OGL::SetVector3(UInt32 loc, Float32 x, Float32 y, Float32 z)
    {
        glUniform3f(loc, x, y, z);
    }

    void Program_OGL::SetVector4(UInt32 loc, Float32 x, Float32 y, Float32 z, Float32 w)
    {
        glUniform4f(loc, x, y, z, w);
    }

    void Program_OGL::SetFloat(UInt32 loc, Float32 v)
    {
        glUniform1f(loc, v);
    }

    void Program_OGL::SetTexture(UInt32 loc, UInt32 unit, Texture *t)
    {
        Texture_OGL *texOGL = static_cast<Texture_OGL*>(t);

        glActiveTexture(GL_TEXTURE0 + unit);
        glBindTexture(texOGL->GetTarget(), texOGL->GetHandle());

        glUniform1i(loc, unit);
    }

    void Program_OGL::SetVector2(const String &name, const Vector2 &v)
    {
        glUniform2f(mShaderMap[name], v.X, v.Y);
    }

    void Program_OGL::SetVector3(const String &name, const Vector3 &v)
    {
        glUniform3f(mShaderMap[name], v.X, v.Y, v.Z);
    }

    void Program_OGL::SetVector4(const String &name, const Vector4 &v)
    {
        glUniform4f(mShaderMap[name], v.X, v.Y, v.Z, v.W);
    }

    void Program_OGL::SetVector2(const String &name, Float32 x, Float32 y)
    {
        glUniform2f(mShaderMap[name], x, y);
    }

    void Program_OGL::SetVector3(const String &name, Float32 x, Float32 y, Float32 z)
    {
        glUniform3f(mShaderMap[name], x, y, z);
    }

    void Program_OGL::SetVector4(const String &name, Float32 x, Float32 y, Float32 z, Float32 w)
    {
        glUniform4f(mShaderMap[name], x, y, z, w);
    }

    void Program_OGL::SetFloat(const String &name, Float32 v)
    {
        glUniform1f(mShaderMap[name], v);
    }

    void Program_OGL::SetTexture(const String &name, UInt32 unit, Texture *t)
    {
        Texture_OGL *texOGL = static_cast<Texture_OGL*>(t);

        glActiveTexture(GL_TEXTURE0 + unit);
        glBindTexture(texOGL->GetTarget(), texOGL->GetHandle());

        glUniform1i(mShaderMap[name], unit);
    }
}
